﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using NRank.Benchmark.Benchmarking;
using NRank.Common;

namespace NRank.Benchmark.Queries
{
	/// <summary>
	/// Base class for benchmark query set
	/// </summary>
	public abstract class BenchmarkQuerySet
	{
		/// <summary>
		/// Result sizes to be tested
		/// </summary>
		public static readonly int[] ValuesOfK = new[] {1, 5, 10, 50, 100, 500, 1000}.Reverse().ToArray();

		//public static readonly int[] ValuesOfK = new[] {1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000};
		//public static readonly int[] ValuesOfK = new[] {1, 10, 50, 100};

		protected double AggregateFunc(double a, double b)
		{
			return Math.Max(a, b);
		}

		/// <summary>
		/// Queries to be tested
		/// </summary>
		public static IEnumerable<BenchmarkQuerySet> Queries
		{
			get
			{
				//yield return new DoubleJoinQuerySet();
				//yield return new JoinOnLargeRight();
				yield return new SimpleJoinQuerySet();
			}
		}

		/// <summary>
		/// Implementation of 'hash rank join - round robin' test
		/// </summary>
		/// <param name="cx">Database context</param>
		/// <returns>Result counted by hash rank join</returns>
		protected abstract IEnumerable<IRanked<JoinResult>> HashRankJoinRoundRobinTest(NRankBenchmarkDbDataContext cx);

		/// <summary>
		/// Implementation of 'hash rank join - adaptive' test
		/// </summary>
		/// <param name="cx">Database context</param>
		/// <returns>Result counted by hash rank join</returns>
		protected abstract IEnumerable<IRanked<JoinResult>> HashRankJoinAdaptiveTest(NRankBenchmarkDbDataContext cx);

		/// <summary>
		/// Implementation of 'rank join - round robin' test
		/// </summary>
		/// <param name="cx">Database context</param>
		/// <returns>Result counted by simple rank join</returns>
		protected abstract IEnumerable<IRanked<JoinResult>> RankJoinTestRoundRobin(NRankBenchmarkDbDataContext cx);

		/// <summary>
		/// Implementation of 'rank join - round robin' test
		/// </summary>
		/// <param name="cx">Database context</param>
		/// <returns>Result counted by 'rank join'</returns>
		protected abstract IEnumerable<IRanked<JoinResult>> RankJoinTestAdaptive(NRankBenchmarkDbDataContext cx);

		/// <summary>
		/// Implementation of nested loop rank join
		/// </summary>
		/// <param name="cx">Database context</param>
		/// <returns>Result counted by nested loop join</returns>
		protected abstract IEnumerable<IRanked<JoinResult>> NestedLoopJoinTest(NRankBenchmarkDbDataContext cx);

		/// <summary>
		/// Implementation of 'nested loop rank join - equality' - test
		/// </summary>
		/// <param name="cx">Database context</param>
		/// <returns>Result counted by nested loop rank join (eqaulity)</returns>
		protected abstract IEnumerable<IRanked<JoinResult>> NestedLoopJoinEqTest(NRankBenchmarkDbDataContext cx);

		/// <summary>
		/// Implementation of traditional SQL test
		/// </summary>
		/// <param name="cx">Database context</param>
		/// <returns>Result counted by hash rank join</returns>
		protected abstract IEnumerable<IRanked<JoinResult>> TraditionalQuery(NRankBenchmarkDbDataContext cx);

		/// <summary>
		/// Measures given query
		/// </summary>
		/// <param name="func">Query to be measured</param>
		/// <param name="algorithmName">Algorithm name</param>
		/// <param name="k">Result size</param>
		/// <returns>Benchmark result of given query</returns>
		private static BenchmarkResult Measure(Func<NRankBenchmarkDbDataContext, IEnumerable<IRanked<JoinResult>>> func, string algorithmName, int k)
		{
			/*using (var cx = CreateContext())
			{
				cx.ExecuteCommand("DBCC FREEPROCCACHE");
				cx.ExecuteCommand("DBCC FREESESSIONCACHE");
				cx.ExecuteCommand("DBCC DROPCLEANBUFFERS");
			}*/

			SqlConnection.ClearAllPools();

			Console.Write("{0}:", algorithmName);

			using (var cx = CreateContext())
			{
				// start measure
				StopWatch.Start();

				// enumerate the results
				var enumerable = func(cx);
				foreach (var _ in enumerable.Take(k))
				{
				}

				int leftDepth = 0;
				int rightDepth = 0;
				if (enumerable is IDepthAwareContainer)
				{
					var cont = enumerable as IDepthAwareContainer;
					leftDepth = cont.LeftDepth;
					rightDepth = cont.RightDepth;
				}

				// end measure
				var result = StopWatch.Measure(k, algorithmName, leftDepth, rightDepth);

				Console.WriteLine(result.TimeConsumed);
				return result;
			}
		}

		/// <summary>
		/// Creates database context
		/// </summary>
		/// <returns>Database context</returns>
		private static NRankBenchmarkDbDataContext CreateContext()
		{
			return new NRankBenchmarkDbDataContext
			       	{
			       		CommandTimeout = 1800,
						Log = Console.Out
			       	};
		}

		/// <summary>
		/// Gets result of all queries for given result size
		/// </summary>
		/// <param name="k">Result size</param>
		/// <returns>Benchmark results for all queries with given k</returns>
		public IEnumerable<BenchmarkResult> GetResults(int k)
		{
			yield return Measure(RankJoinTestRoundRobin, "Obecné tříděné spojení (střídavý výběr vstupu)", k);
			yield return Measure(RankJoinTestAdaptive, "Obecné tříděné spojení (adaptivní výběr vstupu)", k);
			
			yield return Measure(HashRankJoinRoundRobinTest, "Hašované tříděné spojení (střídavý výběr vstupu)", k);
			yield return Measure(HashRankJoinAdaptiveTest, "Hašované tříděné spojení (adaptivní výběr vstupu)", k);

			yield return Measure(NestedLoopJoinTest, "Tříděné spojení s hnízděnými cykly", k);
			yield return Measure(NestedLoopJoinEqTest, "Hašované tříděné spojení s hnízděnými cykly", k);
			//yield return Measure(TraditionalQuery, "MS-SQL dotaz", k);
		}
	}
}